package sg.edu.nus.iss;

import java.text.SimpleDateFormat;
import java.util.*;
import org.jgap.*;
import org.jgap.impl.*;
import java.io.*;

/**
 * 
 * @author Xu Guoneng
 *
 */
public class Main {

	final static int PACKAGE_NUM = 64;
	final static int STACK_NUM = 16;
	final static int STACK_CAPACITY = 4;
	
	static int POPULATION_SIZE;
	static int MAX_EVOLUTION;
	static double MAX_PENALTY;
	static boolean PENALTY_ON;
	static int CONVERGE_TYPE; 		// "0" for Log Sum Diff, "1" for Sum Square Diff
	
	private Package[] packages;
	public static String absPath;
	public static String outputPath;
	
	public static void main(String[] args) {
		absPath = new File(".").getAbsolutePath();
		absPath = absPath.substring(0, absPath.length()-1);
		
		String configFilePath = absPath + "resource/config.txt";
		String dataFilePath = absPath + "resource/data.txt";
		
		new Main(configFilePath, dataFilePath);
	}
	
	public Main(String configFilePath, String dataFilePath){
		init(InputParser.parseConfigFile(configFilePath));
		
		packages = InputParser.parseDataFile(dataFilePath, PACKAGE_NUM);
		Genotype genotype = generateGenotype();
		ArrayList<FitnessResult> results = doEvolution(genotype);
		printSolution(results);
	}
	
	private void init(HashMap<String, String> configs){
		POPULATION_SIZE = Integer.parseInt(configs.get("POPULATION_SIZE"));
		MAX_EVOLUTION = Integer.parseInt(configs.get("MAX_EVOLUTION"));
		MAX_PENALTY = Double.parseDouble(configs.get("MAX_PENALTY"));
		PENALTY_ON = Boolean.parseBoolean(configs.get("PENALTY_ON"));
		CONVERGE_TYPE = Integer.parseInt(configs.get("CONVERGE_TYPE"));
	}
	
	private Genotype generateGenotype(){
		try{
			Configuration gaConf = new DefaultConfiguration();
		    gaConf.resetProperty(Configuration.PROPERTY_FITEVAL_INST);
		    gaConf.setFitnessEvaluator(new DeltaFitnessEvaluator());
		    
		    //use a swapping operator
		    gaConf.getGeneticOperators().clear();
		    SwappingMutationOperator swapper = new SwappingMutationOperator(gaConf);
		    gaConf.addGeneticOperator(swapper);
		    
		    gaConf.setPreservFittestIndividual(true);
		    gaConf.setKeepPopulationSizeConstant(false);
		    gaConf.setPopulationSize(POPULATION_SIZE);
		    
		    int chromeSize = packages.length;
		    IChromosome sampleChromosome = new Chromosome(gaConf, new IntegerGene(gaConf), chromeSize);
		    gaConf.setSampleChromosome(sampleChromosome);
		    
		    //set customised FitnessFunction
		    gaConf.setFitnessFunction(new StackFitnessFunction(packages));
		    
		    //create initial population
		    Genotype genotype = Genotype.randomInitialGenotype(gaConf);
		    InitialPopulationGenerator.generate(genotype, packages);
		    
		    return genotype;
		}
		catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}
	
	private ArrayList<FitnessResult> doEvolution(Genotype genotype){
		ArrayList<FitnessResult> results = new ArrayList<FitnessResult>();
		for (int i = 0; i < MAX_EVOLUTION; i++) {
			genotype.evolve();
			FitnessResult fr = new FitnessResult(i+1, genotype);
			results.add(fr);
		}
		
		return results;
	}
	
	private void printSolution(ArrayList<FitnessResult> results) {
		PrintWriter pw = null;
		
		try{
			GAChart chart = new GAChart();
			
			outputPath = absPath + "results/data_" + new SimpleDateFormat("yyyy-MMM-dd_HHmmss").format(new Date());
			pw =  new PrintWriter(new FileWriter(outputPath  + ".txt", false));

			for(FitnessResult fs : results){
				chart.addBestFitnessSeries(fs.getEvolutionIndex(),fs.getBestFitness());
				chart.addAverageFitnessSeries(fs.getEvolutionIndex(),fs.getAvgFitness());
				pw.println(fs);
			}
			
			pw.println("\n------------------------------\nDetails for the best solution:\n" + 
			results.get(results.size()-1).toDetails(packages));
			pw.flush();
			
			chart.display();
		}
		catch(Exception ex){
			throw new RuntimeException(ex);
		}
		finally{
			if(pw != null){
				pw.close();
			}
		}
	}
}
